<?php

include_once ("../lib/HydraTools/HydraDebug/Console.class.php");
/**
 * This is the description for the class below.
 *
 * @package    HydraTools
 * @subpackage HydraTemplate
 * @author     J
 * @version    2.0.0
 * ...
 */
//class_alias('HydraTemplate', 'template');
final class HydraTemplate extends Loggable {

	public static $version = array("major" => 2, "minor" => 0, "revision" => 0);
	
	private $_cache_age = 43200;	//time in sec
	private $template_path = "";	//Sections storage for the template
	private $_elements, $_template, $_css, $_upperScripts, $_lowerScripts;	//used to hold template from file. Then later as a template cache.
	private $cache;	//Store local ssession
	private $SESSION;	//Lists all sections to a template
	private $sections = array("elements" => "::ELEMENTS::", "topscripts" => "::TOPSCRIPTS::", "bottomscripts" => "::BOTTOMSCRIPTS::", "css" => "::CSS::", "template" => "::TEMPLATE::");
	//Invalid occurances, for debugging
	private $invalid_files;	//Stores execution times of each section to help minimize loading.
	private $exe_times;	//List of sections that should not be exploded into an array.
	private $do_not_explode = array("::TEMPLATE::");

	public function __construct($path, $session = FALSE, $flush = FALSE) {
		$this -> template_path = trim($path);
		if( $flush ){
			$this->flushCache();
		}
		
		if ($session) {
			$this -> logMsg("Using Session" . print_r($session, true));
			$this -> SESSION = $session;
		} 
		
		
		$this -> logEvent("Loading Template: $path");
		$this->checkCache();	
	}

	
	//Loads a specified template file.
	public function loadTemplate() {
		$load_start = microtime();
		
		if (empty($this -> template_path) || !file_exists($this -> template_path)) {
			if (HYDRA_DEBUG) {
				$this -> logEvent("Template (" . $this -> template_path . ") does not exist! [" . getcwd() . "]");
				return false;
			}
		}
		//Open template file
		$this -> logEvent("Importing Template...");
		$this -> _template = file_get_contents($this -> template_path);

		//clean template
		$this -> logEvent("Cleaning Template...");
		$this -> _template = str_replace("\r\n", "\n", trim($this -> _template));
		$this -> _template = explode("\n", $this -> _template);
		$tmp;
		foreach ($this->_template as $idx => $line) {
			$line = trim($line);
			$char_one = substr($line, 0, 1);
			if ($char_one == "#" || $char_one == "") {
				unset($this -> _template[$idx]);
			} else {
				$tmp .= $line . "\n";
				unset($this -> _template[$idx]);
			}
		}
		$this -> _template = $tmp;
		unset($tmp);

		//Load Sections of Template
		$this -> logEvent("Loading template Sections...");
		$this -> _elements = $this -> loadSection("elements");
		$this -> logMsg("Elements Loaded");
		$this -> _css = $this -> loadSection("css");
		$this -> logMsg("CSS Loaded");
		$this -> _lowerScripts = $this -> loadSection("bottomscripts");
		$this -> logMsg("Lower Scripts Loaded");
		$this -> _upperScripts = $this -> loadSection("topscripts");
		$this -> logMsg("Upper Scripts Loaded");
		$this -> _template = $this -> loadSection("template");
		$this -> logMsg("Template Loaded");

		//create cache
		$this->logEvent("Creating Cache...");
		$this->createCache();
		
		$this -> exe_times['Load_Template']['Total'] = (microtime() - $load_start) . " ms";
		return $rtn;
	}
	
	private function checkCache(){
		$this -> logEvent("Checking Cache... (" . $this->template_path . ")");
		if (file_exists($this->template_path . ".cache")) {
			$this -> logMsg("Cache Exists, Checking Age...");
			$timestamp = filemtime($this->template_path . ".cache");
			$now = time();

			$diff = ($now - $timestamp) / 60;
			//Get difference in seconds
			if (!($diff <= $this -> _cache_age) ) {//Load Cache
				$this -> logEvent("Cache is old, rebuilding...");
				$this->loadTemplate();
				$this->createCache();
			}
			$this -> cache = file_get_contents($this->template_path . ".cache");
				$this -> logEvent("Loaded From Cache...");
		} else {
			$this -> logMsg("Cache does not exist.");
			$this->loadTemplate();
			$this->createCache();
		}
		return true;
	}
	private function createCache() {
		$this -> logEvent("createCache() executing...");
		$cache_start = microtime();
		$this -> cache = $this -> _template;
		$css;
		$scripts;
		$this -> logEvent("Creating CSS links...");
		//Construct CSS links
		if (count($this -> _css) > 0) {
			foreach ($this->_css as $idx => $file) {
				$css .= $this -> linkFile($file, "css");
			}
		}
		$this -> logEvent("Injecting CSS...");
		$this -> injectAfter("<head>", $css);

		//Construct Script tags
		//TopScripts
		$this -> logEvent("Creating JS Top Scripts links...");
		if (count($this -> _upperScripts) > 0) {
			foreach ($this->_upperScripts as $idx => $file) {
				if (!empty($file)) {
					$scripts .= $this -> linkFile($file, "js");
					$this -> logMsg("\t$file linked.");
				}
			}
		}
		$this -> logEvent("Injecting Top Scripts...");
		$this -> injectBefore("</head>", $scripts);

		//BottomScripts
		$this -> logEvent("Creating JS Bottom Scripts links...");
		if (count($this -> _lowerScripts) > 0) {
			$scripts = "";
			foreach ($this->_lowerScripts as $idx => $file) {
				if (!empty($file)) {
					$scripts .= $this -> linkFile($file, "js");
					$this -> logMsg("\t$file linked.");
				}
			}
		}
		$this -> logEvent("Injecting Bottom Scripts...");
		$this -> injectAfter("</body>", $scripts);

		//Write cache file w/ timestamp
		$this -> logEvent("Writing Cache to disk...");
		file_put_contents($this -> template_path . ".cache", $this -> cache);
		$cache_stop = microtime();
		$this -> exe_times["Load_Template"]["Create_Cache"] = $cache_stop - $cache_start;
		return true;
	}

	//Returns the file contents of php files, or a link to a css or js file.
	private function linkFile($file, $type = "css") {
		$file = trim($file);
		//echo "<hr>Attempting to LInk: $file of type $type<br /> Current Working Dir: " . getcwd() . "<br />\n\n";
		$link_start = microtime();
		if (empty($file))
			return false;

		$this->logEvent("Linking File: $file as $type");
		//Look for media & extract if present
		$media;
		$media_start = strpos($file, "(");
		if ($media_start > 0) {
			$media_stop = strpos($file, ")", $media_start);
			$media = substr($file, ++$media_start, $media_stop - $media_start);
			$file = trim(substr($file, 0, --$media_start));
		}

		//Get url & local info
		$url_pos = strpos($file, "tp://");
		$local = file_exists(getcwd() . "/" . $file);
		if ($url_pos == 0 && !$local) {//If file is not a url and doesn't exist locally
			$this -> invalid_files[] = $file;
			return false;
		}

		$type = strtolower($type);
		$rtn;

		if (strtolower(substr($file, -4)) == ".php") {//Link processed script return
			//echo "Found PHP script<br />\n";
			$url;
			if ($url_pos > 0) {//Its a URL
				$url = $file;
			} else {
				if ($local) {//If the file exists locally, get its contents
					//echo "<br />USING PASS SESSION: <pre>" . print_r( serialize($_SESSION), true) . "</pre>";
					$url = "$file";
					//echo "<br> URL : $url";
				}
			}
			$link_stop = microtime();
			$this -> exe_times["Link_Files"][$file] = $link_stop - $link_start;
			//echo "Getting contents of $url<br />\n";
			return "\n" . file_get_contents($url);
		} else {//non-php file link
			//Get Modified time of CSS File for caching
			$mod = date("dmYGis");
			$true_path = realpath(getcwd() . "/" . $file);
			//echo $true_path;
			if (file_exists($true_path)) {
				$mod = filemtime($true_path);
				$mod = date("dmYGis", $mod);
			}
			$mod = $file . "?modified=$mod";
			//echo "Not a remote file<br />";
			if ($type == "css") {
				$rtn = "\n<link type=\"text/css\" rel=\"stylesheet\" href=\"$mod\"";

				if (!empty($media)) {
					$rtn .= " media=\"$media\"";
				} else {
					$rtn .= " media=\"all\"";
				}
				$rtn .= " />";
				//echo "Returning Link script for CSS: $rtn <br />\n";
			} else {
				$rtn = "\n<script language=\"javascript\" src=\"$mod\"></script>";
				//echo "Returning Link script for JS: $rtn<br />\n";
			}
		}
		$link_stop = microtime();
		$this -> exe_times["Link_Files"][$file] = $link_stop - $link_start;
		$this->logEvent("Linked File: $file");
		return $rtn;
	}

	private function injectBefore($tag, $content) {
		if (empty($tag) || empty($content))
			return $this -> cache;

		//echo "<br>\nInjecting $content<br>\n";
		$tag = strtolower($tag);
		$lower = strtolower($this -> cache);

		//Find the start of the tag.
		$start_open_tag = strpos($lower, $tag);

		$temp_beginning = substr($this -> cache, 0, $start_open_tag);
		$temp_ending = substr($this -> cache, $start_open_tag);

		$this -> cache = $temp_beginning . $content . $temp_ending;
	}

	private function injectAfter($tag, $content) {

		if (empty($tag) || empty($content) || empty($this -> cache))
			return $this -> cache;

		$tag = strtolower($tag);
		$lower = strtolower($this -> cache);

		//Find the start of the tag, and get the ending bracket (">") We'll insert $content after it.
		$start_open_tag = strpos($lower, $tag);
		$start_open_tag = strpos($lower, ">", $start_open_tag);

		//Split the file after the $tag.
		$temp_beginning = substr($this -> cache, 0, ++$start_open_tag);
		$temp_ending = substr($this -> cache, $start_open_tag);

		unset($lower);

		$this -> cache = $temp_beginning . $content . $temp_ending;
	}

	public function addTextAsScript($content) {
		$this -> injectBefore("</head>", $content);
	}

	//Injection for page-specific top script.
	public function addTopScript($file) {
		$content = $this -> linkFile($file, "js");
		if (!empty($content))
			$this -> injectBefore("</head>", $content);
	}

	//Injection for page-specific bottom script.
	public function addBottomScript($file) {
		$content = $this -> linkFile($file, "js");
		if (!empty($content)) {
			$this -> injectAfter("</body>", $content);
		}
	}

	//Injection for page-specific styles
	public function addCSS($file) {
		$content = $this -> linkFile($file, "css");
		$this -> injectBefore("</head>", $content);
	}

	public function getTimes() {
		$total = 0;
		foreach ($this->exe_times as $source => $time) {
			if ($source == "Total")
				break;
			if (is_array($time)) {
				foreach ($time as $src => $subTime) {
					$total += $subTime;
				}
			} else {
				$total += $time;
			}
		}
		$this -> exe_times[total_time] = $total;
		$output = "<br><div style=\"position:relative;bottom:0px;text-align:left;\">Execution Times:<br><pre>" . print_r($this -> exe_times, true) . "</pre></div>";
		$output = str_ireplace("\t", "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;", $output);
		return $output;
	}

	private function process_static_import(){
		//CHECK FOR STATIC IMPORTS [ <<import path/to/file>>]
		$static_import_start = stripos($this->cache, "<<");
		$static_import_stop = stripos($this->cache, ">>");
		$tmp = substr($this->cache, 0, $static_import_start);
		
		if( $static_import_start || substr($this->cache, 0, 2) == "<<" ){
			$tag = substr($this->cache, $static_import_start, $static_import_stop);		
			if( stripos($tag, "include") ){
				$import_start = stripos($tag, "include") + strlen("include");
				$path = trim( substr($tag, $import_start, stripos($tag, ">>")-$import_start) );
				//echo "Static Import: $path<br/>";exit; 
				if( file_exists(getcwd() . "/$path") ){
					ob_start();
					include $path;
					//echo "Content found: " . ob_get_contents();exit;
					$tmp .= ob_get_contents();
					ob_end_clean();
				}else{
					$tmp .= "Import not found.";
				}
				
			}else{
				$tmp .= "Import not found in $tag.<br>";exit;
			}
			$tmp .= substr($this->cache, $static_import_stop+2);
			$this->cache = $tmp;
		}else{
			return false;
		}
		return true;
	}
	public function printPage($tidy = false, $config = NULL) {
		
		while( $this->process_static_import() ){
			$this->logEvent("Processing Static Import...");
		}
		
		//if(!ob_start("ob_gzhandler")) ob_start();
		$this->logEvent("Printing Page...");
		ob_start();
		if ($tidy) {
			$this->logMsg("Using Tidy...");
			if (empty($config))
				$config = array('indent' => true, 'output' => 'xhtml', 'wrap' => 400);
			try {//Attempt to use Tidy. If tidy does not exist, break and print raw cache
				$b = tidy_parse_string($this -> cache, $config);
				$b -> cleanRepair();
				echo tidy_get_output($b);
			} catch (Exception $ex) {
				$this->logEvent("Error with Tidy!!!");
				return false;
			}
		}else{
			if (empty($this -> cache))
				$this->logEvent("Error with Cache");
			else 
				echo $this-> cache;
		}
		//unset($this -> _template);
		//unset($this -> cache);
		//unset($this);
		//$this->logEvent("Printing Page: " . count(ob_get_contents()) . " bytes");
		ob_flush();
		ob_end_clean();
		return true;
	}

	public function printErrors() {
		echo "<br>Invalid Files: <pre>" . print_r($this -> invalidFiles, true) . "</pre><br>";
	}

	//Destroy all session vars.
	public function destroy() {
		unset($this -> SESSION);
	}

	public function insertSection($element, $content, $remote = false) {
		$insert_start = microtime(true);
		$element = trim($element);
		$content = trim($content);

		if (empty($element) || empty($content))
			return false;
		
		$path_info = getcwd() . "/$content";
		//Check to see if we need to grab a php script
		if (file_exists($path_info) && !is_dir($path_info)) {//LOCAL FILE
			ob_start();
			include_once ($path_info);
			$content = ob_get_contents();
			ob_end_clean();
		} elseif (strtolower(substr($content, 0, 4)) == "http://" && $remote) {//URL
			$content = file_get_contents($content);
			// . arrayToGet($this->SESSION, true));
		}

		$placeholder = $element;
		//Holds the case-sensative version of the placeholder

		//Make sure placeholder is in the sections defined by template file.
		/*if (!is_array($this -> _elements)) {
			$this->logEvent("$element replaced with $content -- FAILED");
			exit ;
		}
		 */
		/*
		foreach ($this->_elements as $section) {
			if (strtolower($element) == strtolower($section)) {
				$placeholder = $section;
				break;
			}
		}
		*/
		/* Strpos returns 0 if string is at beginning. This also represents a false value.
		 * As a double check, get the first bit of cache so we can see if a placeholder is
		 * at the beginning.
		 */
		$start_replace = strpos($this -> cache, $placeholder);
		$start_failsafe = substr($this -> cache, 0, strlen($placeholder));
		$rtn;
		//Remove the placeholder
		if ($start_replace == 0 && $start_failsafe == $placeholder) {
			$rtn = $content . substr($this -> cache, strlen($section));
		} else {
			$rtn = substr($this -> cache, 0, $start_replace);
			$rtn .= $content;
			$rtn .= substr($this -> cache, $start_replace + strlen($element));
		}

		$this -> cache = $rtn;
		$insert_stop = microtime(true);
		$this -> exe_times["Insert_Section"][$element] = $insert_stop - $insert_start;
		return true;
	}

	//used to load separate sections at a time.
	/*
	 * TODO FIx missing section error
	 */
	private function loadSection($name) {
		//echo "Loading Section: $name<br />";
		$start = microtime(true);
		
		if( empty($name) ) return false;
		$name = strtolower($name);
		/*if (!array_key_exists($name, $this -> sections))
			return false;
		*/
		$start = strpos($this -> _template, $this -> sections[$name]);
		$start_failsafe;
		if (!$start)
			$start_failsafe = substr($this -> _template, 0, strlen($this -> sections[$name]));

		$section_len = strlen($this -> sections[$name]);

		if (!$start && ($start_failsafe !== $this -> sections[$name]) )
			return false;

		$stop = strpos($this -> _template, "::", ($start + $section_len));
		if (!$stop)
			$rtn = substr($this -> _template, $start + $section_len);
		else
			$rtn = substr($this -> _template, $start + $section_len, $stop - ($start + $section_len));

		//Normalize
		$rtn = str_replace("\r\n", "\n", $rtn);
		$rtn = str_replace("\t", "", $rtn);
		//Throw to array unless this element is in $do_not_explode
		if (!in_array($this -> sections[$name], $this -> do_not_explode))
			$rtn = explode("\n", $rtn);
		//echo "Section found: " . print_r($rtn, true) . "<br><br>";
		$stop = microtime(true);
		$this->exe_times["Load_Section (" . $name . ")"] = $stop - $start;
		return $rtn;
	}

	//Tools
	private function arrayToGet($array, $first = true) {
		//Add parent to array
		$array[_PARENT_] = $_SERVER[SCRIPT_NAME];
		$array[_CLIENTIP_] = $_SERVER[REMOTE_ADDR];
		$array = array_merge($array, $_GET);
		$rtn;
		if (!is_array($array))
			return false;

		if ($first)
			$rtn = "?";
		foreach ($array as $idx => $value) {
			if (is_array($value)) {
				$rtn .= arrayToGet($value);
			} else {
				if (!$first)
					$rtn .= "&";
				$rtn .= "$idx=" . urlencode($value);
				$first = false;
			}
		}
		if (count($_GET) > 0 && $first) {
			$rtn .= arrayToGet($_GET);
		}
		return $rtn;
	}

	//Extra funcs
	public static function getVersion() {
		$major = self::$version['major'];
		$minor = self::$version['minor'];
		$revision = self::$version['revision'];

		echo "v$major.$minor r$revision";
	}
	public function flushCache(){
		if( !empty($this->template_path) ){
			if ( file_exists($this->template_path . ".cache") ){
				$this->logEvent("Flushing Cache...");
				unlink($this->template_path . ".cache");
				return true;
			}
		}
		return false;
	}

}
?>